from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QGridLayout, QApplication, QComboBox, QPushButton, QLabel, QPlainTextEdit, QMessageBox
from PyQt5.QtGui import QFont
from sys import argv, exit

import serial
import serial.tools.list_ports

import re


def print_me(thing: bytes):
    return ''.join(
        f'\\x{byte:02x}'
        for byte in thing)

class Binary:
    """
        自定义进制转化
    """
    @staticmethod
    def Hex2Dex(e_hex):
        """
        十六进制转换十进制
        :param e_hex:
        :return:
        """
        return int(e_hex, 16)

    @staticmethod
    def Hex2Bin(e_hex):
        """
        十六进制转换二进制
        :param e_hex:
        :return:
        """
        return bin(int(e_hex, 16))

    @staticmethod
    def Dex2Bin(e_dex):
        """
        十进制转换二进制
        :param e_dex:
        :return:
        """
        return bin(e_dex)

class CRC:
    """
     CRC验证
    """
    def __init__(self):
        self.Binary = Binary()

    def CRC16(self, hex_num):
        """
        CRC16校验

        :param hex_num:
        :return:
        """
        crc = '0xffff'
        crc16 = '0xA001'
        # test = '01 06 00 00 00 00'
        test = hex_num.split(' ')
        print(test)

        crc = self.Binary.Hex2Dex(crc)  # 十进制
        crc16 = self.Binary.Hex2Dex(crc16)  # 十进制
        for i in test:
            temp = '0x' + i
            # 亦或前十进制准备
            temp = self.Binary.Hex2Dex(temp)  # 十进制
            # 亦或
            crc ^= temp  # 十进制
            for i in range(8):
                if self.Binary.Dex2Bin(crc)[-1] == '0':
                    crc >>= 1
                elif self.Binary.Dex2Bin(crc)[-1] == '1':
                    crc >>= 1
                    crc ^= crc16
                # print('crc_D:{}\ncrc_B:{}'.format(crc, self.Binary.Dex2Bin(crc)))

        crc = str(hex(crc))
        if len(crc) == 5:
            crc = "0" + crc[2:]
            crc_H = crc[:2]
            crc_L = crc[2:]
        else:
            crc_H = crc[2:4]
            crc_L = crc[-2:]

        return crc, crc_H, crc_L

class Modbus_Channel(QWidget):

    def __init__(self):
        super(Modbus_Channel, self).__init__()
        self.setWindowTitle("串口通信")
        self.setWindowModality(Qt.WindowModal)

        self.resize(800, 600)
        self.setStyleSheet('background-color: #ffffff;')

        self.setup_ui()

        self.CRC = CRC()

    def setup_ui(self):
        
        self.VBoxLayout_ALL = QHBoxLayout()     # 总布局
        
        self.GridLayout_L = QGridLayout()       # 左侧布局

        self.GridLayout_R = QGridLayout()       # 右侧布局
        
        self.VBoxLayout_ALL.addLayout(self.GridLayout_L)
        self.VBoxLayout_ALL.addLayout(self.GridLayout_R)

        font = QFont()
        font.setFamily('SimHei')
        font.setPixelSize(14)

        self.COM_PORT_LABEL = QLabel("串口号:")
        self.COM_PORT_LABEL.setFont(font)
        self.COM_SHOW = QComboBox()
        self.COM_SHOW.setFixedWidth(80)
        self.COM_SHOW.setFont(font)
        self.COM_DETECT = QPushButton("串口检测")
        self.COM_DETECT.setFixedWidth(100)
        self.COM_DETECT.setFont(font)
        self.GridLayout_L.addWidget(self.COM_PORT_LABEL, 1, 1, 1, 1)
        self.GridLayout_L.addWidget(self.COM_SHOW, 1, 2, 1, 1)
        self.GridLayout_L.addWidget(self.COM_DETECT, 1, 3, 1, 1)

        self.BRAUD_RATE_LABEL = QLabel("波特率")
        self.BRAUD_RATE_LABEL.setFont(font)
        BRAUD_RATE = ['9600', '19200', '38400', '57600', '115200']
        self.BRAUD_RATE = QComboBox()
        self.BRAUD_RATE.setFixedWidth(80)
        self.BRAUD_RATE.setFont(font)
        self.BRAUD_RATE.addItems(BRAUD_RATE)
        self.OPEN_COM = QPushButton("打开串口")
        self.OPEN_COM.setFixedWidth(100)
        self.OPEN_COM.setFont(font)

        self.GridLayout_L.addWidget(self.BRAUD_RATE_LABEL, 2, 1, 1, 1)
        self.GridLayout_L.addWidget(self.BRAUD_RATE, 2, 2, 1, 1)
        self.GridLayout_L.addWidget(self.OPEN_COM, 2, 3, 1, 1)


        self.WRITE_SINGAL_COMMAND = QPushButton("单圈")
        self.WRITE_MULTIP_COMMAND = QPushButton("多圈")
        self.WRITE_COILS_COMMAND = QPushButton("圈数")
        self.WRITE_ZERO_COMMAND = QPushButton("零点")
        self.WRITE_BRAUD_COMMAND = QPushButton("波特率")
        self.WRITE_DIRECTION_COMMAND = QPushButton("旋转方向")


        self.WRITE_SINGAL_COMMAND.setFixedWidth(60)
        self.WRITE_MULTIP_COMMAND.setFixedWidth(60)
        self.WRITE_COILS_COMMAND.setFixedWidth(60)
        self.WRITE_ZERO_COMMAND.setFixedWidth(60)
        self.WRITE_BRAUD_COMMAND.setFixedWidth(60)
        self.WRITE_DIRECTION_COMMAND.setFixedWidth(60)

        self.WRITE_SINGAL_COMMAND.setFont(font)
        self.WRITE_MULTIP_COMMAND.setFont(font)
        self.WRITE_COILS_COMMAND.setFont(font)
        self.WRITE_ZERO_COMMAND.setFont(font)
        self.WRITE_BRAUD_COMMAND.setFont(font)
        self.WRITE_DIRECTION_COMMAND.setFont(font)

        self.GridLayout_L.addWidget(self.WRITE_BRAUD_COMMAND, 3, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_ZERO_COMMAND, 4, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_DIRECTION_COMMAND, 5, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_SINGAL_COMMAND, 6, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_MULTIP_COMMAND, 7, 1, 1, 3, Qt.AlignCenter)
        self.GridLayout_L.addWidget(self.WRITE_COILS_COMMAND, 8, 1, 1, 3, Qt.AlignCenter)


        self.SEND_DATA_LABEL = QLabel("发送指令:")
        self.SEND_DATA_LABEL.setFont(font)

        self.SEND_DATA = QLabel()
        self.SEND_DATA.setFont(font)
        self.SEND_DATA.setFixedWidth(150)

        self.GridLayout_L.addWidget(self.SEND_DATA_LABEL, 9, 1, 1, 1)
        self.GridLayout_L.addWidget(self.SEND_DATA, 9, 2, 1, 2)

        self.RETURN_DATA_LABEL = QLabel("返回数据:")
        self.RETURN_DATA_LABEL.setFont(font)

        self.RETURN_DATA = QLabel()
        self.RETURN_DATA.setFont(font)
        self.RETURN_DATA.setFixedWidth(150)
        self.GridLayout_L.addWidget(self.RETURN_DATA_LABEL, 10, 1, 1, 1)
        self.GridLayout_L.addWidget(self.RETURN_DATA, 10, 2, 1, 2)


        self.SINGAL_VALUE_LABEL = QLabel("单圈值：")
        self.SINGAL_VALUE_LABEL.setFont(font)
        self.SINGAL_VALUE = QLabel()
        self.SINGAL_VALUE.setFixedWidth(85)
        self.SINGAL_VALUE.setFont(font)
        self.GridLayout_L.addWidget(self.SINGAL_VALUE_LABEL, 11, 1, 1, 1)
        self.GridLayout_L.addWidget(self.SINGAL_VALUE, 11, 2, 1, 2)

        self.MUTIPLE_VALUE_LABEL = QLabel("多圈值：")
        self.MUTIPLE_VALUE_LABEL.setFont(font)
        self.MUTIPLE_VALUE = QLabel()
        self.MUTIPLE_VALUE.setFixedWidth(85)
        self.MUTIPLE_VALUE.setFont(font)
        self.GridLayout_L.addWidget(self.MUTIPLE_VALUE_LABEL, 12, 1, 1, 1)
        self.GridLayout_L.addWidget(self.MUTIPLE_VALUE, 12, 2, 1, 2)

        self.COIL_VALUE_LABEL = QLabel("圈数值：")
        self.COIL_VALUE_LABEL.setFont(font)
        self.COIL_VALUE = QLabel()
        self.COIL_VALUE.setFixedWidth(85)
        self.COIL_VALUE.setFont(font)
        self.GridLayout_L.addWidget(self.COIL_VALUE_LABEL, 13, 1, 1, 1)
        self.GridLayout_L.addWidget(self.COIL_VALUE, 13, 2, 1, 2)

        self.AGLE_VALUE_LABEL = QLabel("角度：")
        self.AGLE_VALUE_LABEL.setFont(font)
        self.AGLE_VALUE = QLabel()
        self.AGLE_VALUE.setFixedWidth(85)
        self.AGLE_VALUE.setFont(font)
        self.GridLayout_L.addWidget(self.AGLE_VALUE_LABEL, 14, 1, 1, 1)
        self.GridLayout_L.addWidget(self.AGLE_VALUE, 14, 2, 1, 2)

        self.TEXT_EDIT = QPlainTextEdit()
        self.TEXT_EDIT.setFixedWidth(600)
        self.TEXT_EDIT.setFixedHeight(600)
        self.GridLayout_R.addWidget(self.TEXT_EDIT)


        self.setLayout(self.VBoxLayout_ALL)

        self.COM_DETECT.clicked.connect(self.COM_DETECT_FUNCTION)
        self.COUNT = 0
        self.OPEN_COM.clicked.connect(self.OPEN_CLOSE_TRANS)

        self.WRITE_SINGAL_COMMAND.clicked.connect(self.SIGNAL_VALUE_FUNCTION)
        self.WRITE_MULTIP_COMMAND.clicked.connect(self.MUTIPLE_VALUE_FUNCTION)
        self.WRITE_COILS_COMMAND.clicked.connect(self.COIL_VALUE_FUNCTION)
        self.WRITE_ZERO_COMMAND.clicked.connect(self.SET_ZERO_VALUE_FUNCTION)
        self.WRITE_BRAUD_COMMAND.clicked.connect(self.BAUD_VALUE_FUNCTION)
        self.WRITE_DIRECTION_COMMAND.clicked.connect(self.SET_DIRECTION_FUNCTION)

    def COM_DETECT_FUNCTION(self):
        port_list = list(serial.tools.list_ports.comports())
        port_name = []
        for port in port_list :
            port_name.append(port[0])

        self.COM_SHOW.addItems(port_name)

    def SERIAL_OPEN(self):
        port_name = self.COM_SHOW.currentText()
        braud_rate = int(self.BRAUD_RATE.currentText())
        try :
            ser = serial.Serial(port=port_name,
                                 baudrate=braud_rate,
                                 parity=serial.PARITY_NONE,
                                 bytesize=8,
                                 stopbits=1,
                                 timeout=1)
            return ser
        except serial.serialutil.SerialException as f:
            print(f)
            return None

    def OPEN_CLOSE_TRANS(self):

        if self.COUNT % 2 == 0 :
            self.OPEN_COM.setText("打开串口")
            ser = self.SERIAL_OPEN()
            # ser.open()
            print(ser)
        else :
            self.OPEN_COM.setText("关闭串口")
            ser = self.SERIAL_OPEN()
            ser.close()
            print(ser)
        self.COUNT += 1

        return ser
    '''发送报文'''
    def SEND_MESSAGE(self, data):
        ser = self.SERIAL_OPEN()
        sendbytes = 0
        if ser != None :
            # 生成CRC16校验码
            crc, crc_H, crc_L = self.CRC.CRC16(data)
            # 生成完整报文
            sendbytes = data + ' ' + crc_L + ' ' + crc_H + " " + '\r\n'
            self.SEND_DATA.setText(sendbytes)  # 显示到发送数据label
            Byte_HEX = bytes.fromhex(sendbytes)
            ser.write(Byte_HEX)  # 发送给编码器
            RECEIVE_RX_DECODE = ser.readline()
            RECEIVE_RX_HEX = RECEIVE_RX_DECODE.hex()
            DATA = self.SEND_DATA.text()
            if RECEIVE_RX_DECODE != "":
                SIGNAL_VALUE_LIST = re.findall(".{2}", RECEIVE_RX_HEX)  # 转化为列表
                SIGNAL_VALUE = " ".join(SIGNAL_VALUE_LIST)  # 转换为字符串
                self.RETURN_DATA.setText(SIGNAL_VALUE)
                VALUE_DATA = ""
                if DATA[4] == '3':
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[6:-4]
                elif DATA[4] == '6':
                    if DATA[10] == '8':
                        VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[8:-4]
                    else:
                        VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[10:-4]

                INT_VALUE = int(VALUE_DATA, 16)  # 16->10 进制

                return INT_VALUE

        else:
            pass
    # 接收报文
    def RECEIVE_MESSAGE(self):
        DATA = self.SEND_DATA.text()

        RECEIVE_RX_DECODE = None
        ser = self.SERIAL_OPEN()
        if ser != None :
            RECEIVE_RX_DECODE = ser.readline().decode("UTF-8")  # 编码器反馈的十六进制字节
        else : pass
        if RECEIVE_RX_DECODE != '':
            SIGNAL_VALUE = RECEIVE_RX_DECODE.replace("\\x", " ")  # 数据部分，并去掉空格
            self.RETURN_DATA.setText(SIGNAL_VALUE)
            VALUE_DATA = ""
            if DATA[4] == '3' :
                VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[6:-4]
            elif DATA[4] == '6' :
                if DATA[10] == '8' :
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[8:-4]
                else :
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[10:-4]

            INT_VALUE = int(VALUE_DATA, 16)  # 16->10 进制

            return INT_VALUE

        else:
            STR_VALUE = '单圈值：0\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
            print(QMessageBox.warning(self, '警告', '串口没有接收到数据', QMessageBox.Yes, QMessageBox.Yes))
    # 接收多圈报文
    def RECEIVE_MUTIPLE_MESSAGE(self):
        DATA = self.SEND_DATA.text()
        ser = self.SERIAL_OPEN()
        RECEIVE_RX_DECODE = ser.readline()  # 编码器反馈的十六进制字节
        RECEIVE_RX_HEX = RECEIVE_RX_DECODE.hex()
        if RECEIVE_RX_DECODE != '':

            SIGNAL_VALUE_LIST = re.findall(".{2}", RECEIVE_RX_HEX)  # 转化为列表
            SIGNAL_VALUE = " ".join(SIGNAL_VALUE_LIST)  # 转换为字符串
            self.RETURN_DATA.setText(SIGNAL_VALUE)
            VALUE_DATA = ""
            if DATA[4] == '3':
                VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[6:-4]
            elif DATA[4] == '6':
                if DATA[10] == '8':
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[8:-4]
                else:
                    VALUE_DATA += SIGNAL_VALUE.replace(" ", "")[10:-4]

            INT_VALUE = int(VALUE_DATA, 16)  # 16->10 进制

            return INT_VALUE

        else:
            STR_VALUE = '单圈值：0\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
            print(QMessageBox.warning(self, '警告', '串口没有接收到数据', QMessageBox.Yes, QMessageBox.Yes))
    # 单圈值
    def SIGNAL_VALUE_FUNCTION(self):
        INT_SIGNAL_VALUE = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0 :
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else :
            # 基础报文
            READ_ENCODER_VALUE = '01 03 00 00 00 01'    # 指令
            self.SEND_MESSAGE(READ_ENCODER_VALUE)
            INT_VALUE = self.SEND_MESSAGE(READ_ENCODER_VALUE)
            STR_VALUE = '单圈值：' + str(INT_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
            self.SINGAL_VALUE.setText(str(INT_VALUE))

            # AGLE = INT_VALUE / 1024
            # self.AGLE_VALUE.setText(AGLE)
    # 多圈值
    def MUTIPLE_VALUE_FUNCTION(self):
        INT_MUTIPLE_VALLUE = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:
            READ_ENCODER_MULTI_TRUN_VALUE = '01 03 00 00 00 02'     # 指令

            self.SEND_MESSAGE(READ_ENCODER_MULTI_TRUN_VALUE)
            INT_MUTIPLE_VALUE = self.MUTIPLE_VALUE_FUNCTION()

            STR_VALUE = '多圈值：' + str(INT_MUTIPLE_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
            self.MUTIPLE_VALUE.setText(str(INT_MUTIPLE_VALLUE))
    # 圈数值
    def COIL_VALUE_FUNCTION(self):
        INT_COIL_VALUE = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:

            READ_ENCODER_COIL_VALUE = '01 03 00 02 00 01'  # 指令
            self.SEND_MESSAGE(READ_ENCODER_COIL_VALUE)
            INT_COIL_VALUE = self.RECEIVE_MESSAGE()
            self.COIL_VALUE.setText(INT_COIL_VALUE)
            STR_VALUE = '圈数：' + str(INT_COIL_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
    # 设置编码器波特率  ->  04:波特率115200
    def BAUD_VALUE_FUNCTION(self):
        INT_BAUD_VALUE = 0
        BRAUT_RATE = self.BRAUD_RATE.currentText()
        if BRAUT_RATE == '9600' :
            END_STR = '00'
        elif BRAUT_RATE == '19200' :
            END_STR = '01'
        elif BRAUT_RATE == '38400' :
            END_STR = '02'
        elif BRAUT_RATE == '57600' :
            END_STR = '03'
        else :
            END_STR = '04'
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:
            SET_ENCODER_BAUD_RATE = '01 06 00 04 00 ' + END_STR  # 指令

            self.SEND_MESSAGE(SET_ENCODER_BAUD_RATE)
            INT_BAUD_VALUE = self.RECEIVE_MESSAGE()
            if INT_BAUD_VALUE == 0 :    INT_BAUD_VALUE = 9600
            elif INT_BAUD_VALUE == 1 :  INT_BAUD_VALUE = 19200
            elif INT_BAUD_VALUE == 2 :  INT_BAUD_VALUE = 38400
            elif INT_BAUD_VALUE == 3 :  INT_BAUD_VALUE = 57600
            elif INT_BAUD_VALUE == 4 :  INT_BAUD_VALUE = 115200
            else : pass
            STR_VALUE = '波特率：' + str(INT_BAUD_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
    # 设置零点
    def SET_ZERO_VALUE_FUNCTION(self):
        INT_ZERO_VALUE = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:

            SET_ENCODER_ZEROPOINT = '01 06 00 08 00 01'  # 指令
            self.SEND_MESSAGE(SET_ENCODER_ZEROPOINT)
            INT_ZERO_VALUE = self.RECEIVE_MESSAGE()

            if INT_ZERO_VALUE == 1 :
                INT_ZERO_VALUE = 0
            else : pass

            STR_VALUE = '置零：' + str(INT_ZERO_VALUE) + '\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)
    # 设置旋转方向 -> 00:顺时针 01:逆时针
    def SET_DIRECTION_FUNCTION(self):
        INT_SET_DIRECTION = 0
        if self.COUNT == 0 or self.COUNT % 2 == 0:
            print(QMessageBox.warning(self, '警告', '请先打开串口', QMessageBox.Yes, QMessageBox.Yes))
        else:
            SET_ENCODER_ROLL_DIRECTION = '01 06 00 09 00 00'  # 指令
            self.SEND_MESSAGE(SET_ENCODER_ROLL_DIRECTION)
            INT_SET_DIRECTION = self.RECEIVE_MESSAGE()

            if INT_SET_DIRECTION == 0 :     INT_SET_DIRECTION = '顺时针'
            elif INT_SET_DIRECTION == 1 :   INT_SET_DIRECTION = '逆时针'
            else : pass

            STR_VALUE = '旋转方向：' + str(INT_SET_DIRECTION) + '\r\n'
            self.TEXT_EDIT.insertPlainText(STR_VALUE)


if __name__ == '__main__':
    app = QApplication(argv)
    MainWindow = Modbus_Channel()
    MainWindow.show()
    exit(app.exec_())
